#include "Process.h"

extern "C" {
	void* threadWrapper(void* args) {
		Process* proc = (Process*)args;
		//menu["rip_time_space_continuum"](**&params);
		proc->run();
		return (void*)NULL;
	}
}

Process::Process(System* sys):sys(sys) {
	for(int i=0; i<12; i++) {
		fdTable[i]=-1;
	}
	strcpy(cwd, "/");
	start();
}

Process::Process(System* sys, Process* parent):sys(sys) {
	for(int i=0; i<12; i++) {
		fdTable[i]=parent->fdTable[i];
		if(parent->fdTable[i]!=-1) {
			sys->incReferenceCount(parent->fdTable[i]);
		}
	}
	strcpy(cwd, parent->cwd);
	start();
}

Process::~Process() {
	pthread_mutex_destroy(&m_InboxLock);
	pthread_cond_destroy(&m_InboxCond);
}


void Process::start(){
	cout << "Process::start"<<endl;
	busy = true;
	// Initialize the thread itself
	if (pthread_mutex_init(&m_InboxLock, NULL) != 0)
		{
			perror("inbox lock initialization");
			exit(1);
		}
	if (pthread_cond_init(&m_InboxCond, NULL) != 0)
	{
		perror("cond var initialization");
		exit(1);
	}

	if ( pthread_create(&m_thread, NULL, threadWrapper, this) != 0 )
	{
		perror("thread creation failed");
		exit(1);
	}
}

bool Process::isBusy() {
//	pthread_mutex_lock(&m_InboxLock);
//	bool ans = !inbox.empty();
//	pthread_mutex_unlock(&m_InboxLock);
//	return ans;
	return busy;
}


void Process::enqueCmd(vector<string> cmd) {
	//cout << "Process::enqueCmd"<<endl;
	busy=true;
	pthread_mutex_lock(&m_InboxLock);
	inbox.push(cmd);
	// signal if there is any one waiting
	pthread_cond_signal(&m_InboxCond);
	pthread_mutex_unlock(&m_InboxLock);
}

void Process::run(){
	cout << "Process::run"<<endl;
	while(true) {
		pthread_mutex_lock(&m_InboxLock);
		while(inbox.empty()) {
			cout << "Process::run going to sleep"<<endl;
			busy=false;
			pthread_cond_wait(&m_InboxCond, &m_InboxLock);
			cout << "Process::run woke up"<<endl;
		}
		vector<string> cmd = inbox.front();
		inbox.pop();
		pthread_mutex_unlock(&m_InboxLock);
		runCmd(cmd, this);
	}
}


const char* Process::getCurrentWorkingDirectoryForTomer() {
	return cwd;
}


void Process::status() {
	//cout << "Process status:"<<endl;
	cout << "inbox has "<< inbox.size()<<" commands, fdTable = {"<<fdTable[0];
    for(int i=1; i<12; i++) {
	  cout <<", "<<fdTable[i];
    }
    cout << "} and pwd="<< cwd << endl;
//	for(queue< vector<const char*> >::iterator it=inbox.begin() ; it != inbox.end(); it++ ) {
//		cout << "command: ";
//		for (vector<const char*>::iterator it2=it->begin() ; it2 < it->end(); it2++ )
//			cout << *it2 << " ";
//		cout <<endl;
//	}

}

char* Process::getFullPath(char* buf, const char* dirname) {
	if(dirname[0]=='/') {
		strcpy(buf, "/"); //absolute path
		dirname = dirname+1;
	}
	//relative:
	else
		strcpy(buf, cwd);
	if(strcmp(dirname, ".")==0)
		return buf;
///////////////////
	string str(dirname);
	string delimiters("/");
	for(string::size_type lastPos = str.find_first_not_of(delimiters, 0),
		pos     = str.find_first_of(delimiters, lastPos);
		string::npos != pos || string::npos != lastPos;
		lastPos = str.find_first_not_of(delimiters, pos),
        pos = str.find_first_of(delimiters, lastPos))   {
			string stok=str.substr(lastPos, pos - lastPos);
			const char* tok = stok.c_str();
///////////////////

			//cout << "Process::getFullPath iteration: buf='" << buf << "'   tok="<<tok << endl;
			if(strcmp(tok, "..")==0) { //pop
				if(strlen(buf)>2) {
					*strrchr(buf, '/')='\0';
					*(strrchr(buf, '/')+1)='\0';
				}
			}
			else if(strcmp(tok, ".")==0) ;//ignore
			else {strcat(buf, tok);//push
			strcat(buf, "/");
			}
		}

	if(dirname[strlen(dirname)-1]!='/')
		buf[strlen(buf)-1]='\0';
	return buf;
}

int Process::mkdir(const char* dirname) {
	cout << "Process::mkdir "<< dirname << endl;
	char temp[256];
	return sys->MakeDir(getFullPath(temp, dirname));
}

int Process::getLowestFreeFd() {
	for(int i=0; i<12; i++) {
		if(fdTable[i]==-1)
			return i;
	}
	return -1; //:(
}
//creates an empty file. Returns a file descriptor <fd>.
int Process::create(const char* filename, int flags) {
	int pfdi=getLowestFreeFd();
	if(pfdi == -1) {
		cout<<"Process::create won't create the file because process "<<proc<<" already has 12 open fd's."<<endl;
		return -1;
	}
	char temp[256];
	int fd = sys->MakeFile(getFullPath(temp, filename), POFILE, flags);
	if(fd==-1){
		cout<<"Process::create couldn't create file "<<filename<<" :("<<endl;
		return -1;
	}
	fdTable[pfdi]=fd;
	return pfdi;
}

//- creates a hard link to file-to-link-to.
//Making a hard link to directories is not allowed.
int Process::hdlink(const char* target, const char* linkname) {
	char temp1[256];
	char temp2[256];
	return sys->MakeHLink(getFullPath(temp1, target), getFullPath(temp2, linkname));
}//<file-to-link-to><new-file-name>

// -creates a soft link to dest-to-link-to.
//dest can be either a directory or a file.
int Process::sflink(const char* target, const char* linkname) {
	char temp[256];
	int fd = sys->MakeFile(getFullPath(temp, linkname), SOFTLINK, WRITE);
	if(fd==-1) {
		cout << "Process::sflink: couldn't create soft link '"<<linkname<<"' "<<endl;
		return -1;
	}
	getFullPath(temp, target);
	if(sys->Write(fd, strlen(temp)+1, (byte*)temp) < strlen(temp)) {
		cout << "Process::sflink: couldn't write to soft link '"<<linkname<<"', deleting "<<endl;
		sys->Close(fd);
		sys->RmFile(linkname);
		return -1;
	}
	return sys->Close(fd);
}

//- opens an existing file returning a file descriptor <fd>.
//The fd returned should always be the lowest one still free.
//Flag can be set to "read-only" or "read-and-write".
//Always remember to check when reading or writing that the file was actually
//opened for that purpose, otherwise return an error. Your flags should be in
//the exact syntax specified.
int Process::open(const char* filename, int flag) {
	int pfdi=getLowestFreeFd();
	if(pfdi == -1) {
		cout<<"Process::open won't open '"<<filename<<"' because process "<<proc<<" already has 12 open fd's."<<endl;
		return -1;
	}
	char temp[256];
	int fd = sys->Open(getFullPath(temp, filename), flag, 0);
	if(fd==-1){
		cout<<"Process::open couldn't open file "<<filename<<" :("<<endl;
		return -1;
	}
	fdTable[pfdi]=fd;
	return pfdi;
}

// - close the file associated with the given file descriptor.
//Release locks on this file held by this process.
int Process::close(int pfdi) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::close:  bad pfdi "<<pfdi<<endl;
		return -1;
	}

	if(sys->Close(fdTable[pfdi])!=0) {
		cout << "Process::close:  WARNING: the file associated with "<<pfdi<<" was already closed, god knows why!"<<endl;
		return -1;
		//todo: check why?
	}
	fdTable[pfdi]=-1;
	return 0;
}

//Read num-bytes from the file that is identified by fd.
//Returns the string read, followed by a newline and then the number of bytes
//that were actually read. Remember read uses the current file
//pointer to know where to start reading from, and the operation
//advances the file pointer. The output should be shown on stdout.
int Process::read(int pfdi, int nBytes) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::read:  bad pfdi "<<pfdi<<endl;
		return -1;
	}
	int fd=fdTable[pfdi];
	byte buf[128];
	//cout << "reading at most "<<nBytes<<" bytes:"<<endl;
	int n=0;
	while(n<nBytes) {
		int r = sys->Read(fd, min(127, nBytes-n), buf);
		if(r<=0)
			break;
		buf[r]='\0';
		cout << buf;
		n += r;
	}
	cout << endl << "total of "<< n <<" bytes were read."<<endl;
	return n;
}

// - writes the string to the file. Returns the number of bytes actually written.
//Remember write uses the current file pointer to know where to start writing from,
//and the operation advances the file pointer.
int Process::write(int pfdi, const char* str) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::write:  bad pfdi "<<pfdi<<endl;
		return -1;
	}
	int fd=fdTable[pfdi];
	int n = sys->Write(fd, strlen(str), (byte*)str);
	cout << "Process::write: total of "<< n <<" bytes were written."<<endl;
	return 0;
}


// -imports a real file from linux's file system from the given address into the
// file fd. In this way files can be imported into the system.
int Process::writeFile(int pfdi,  const char* realfile) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::write:  bad pfdi "<<pfdi<<endl;
		return -1;
	}
	int fd=fdTable[pfdi];
	FILE* f = fopen(realfile, "rb");
	if (f==NULL) {
		perror ("Error opening file");
		return -1;
	}

	fseek (f , 0 , SEEK_END);
	long lSize = ftell (f);
    rewind (f);

	byte buf[lSize];
	fread(buf, 1, lSize, f);
	fclose (f);
	int n = sys->Write(fd, lSize, buf);
	if (n<0) {
		cout << "Process::writeFile: System call to write failed - probably no write permission."<<endl;
		return -1;
	}
	cout << "Process::writeFile: total of "<< n <<" bytes were written."<<endl;
	return 0;
}

// <file-name> - deletes the file.
int Process::rm(const char* filename) {
	char temp[256];
	return sys->RmFile(getFullPath(temp, filename));
}

// - deletes the directory. A directory can only be deleted if it is empty.
int Process::rmdir(const char* dirname) {
	char temp[256];
	return sys->RmDir(getFullPath(temp, dirname));
}

// - recursively deletes directory, all sub-directories and all files.
//If one of the files is open, terminate and return error. It is o.k.
//if the system starts deleting files and then finds one open, to then return an error.
//This means that the command may be executed partially.
int Process::rmdir_r(const char* dirname) {
	char temp[256];
	return sys->RmDir_r(getFullPath(temp, dirname));
}


int Process::fd(){
	int c=0;
	for(int i=0; i<12; i++) {
		if(fdTable[i]!=-1)
			c++;
	}

	cout<<"There are "<< c << " open files for proc "<<proc <<":"<<endl;
	for(int i=0; i<12; i++) {
		if(fdTable[i]!=-1)
			printf("process fd %d  ->  system fd %d\n", i,fdTable[i]);
	}
	return 0;
}

// - returns a list of the files in the given directory, each file in a different
//line and including the file-name and its size (in bytes). If ls is given
//without an argument, the ls of the current directory should be returned.
//Output should be as follows: each file/dir in the given directory in a separate line,
//directories have the "/" char appended to the end of their name.

int Process::ls(const char*dirname, char* buf) {
	char temp[256];
	if(sys->ls(getFullPath(temp, dirname),buf)==-1){
		//cout<<"Process::ls is sad because System::ls failed."<<endl;
		return -1;
	}
	return 0;

}

int Process::ls(const char* dirname) {
	//cout << "Process::ls "<< dirname << endl;
	char temp[256];
	if(sys->ls(getFullPath(temp, dirname))==-1){
		cout<<"Process::ls is sad because System::ls failed."<<endl;
		return -1;
	}
	return 0;
}

// - changes the old-file-name to new file name. This can be just changing its
//name in the same directory, or even moving the file to a different directory.
int Process::mv(const char *oldfilename, const char* newfilename) {
	char temp1[256];
	char temp2[256];
	return sys->moveFile(getFullPath(temp1, oldfilename), getFullPath(temp2, newfilename));
}

// - changes current working directory of the current process to new-directory.
int Process::cd(const char* dirname) {
	char temp[256];
	getFullPath(temp, dirname);
	if(!sys->isDir(temp)) {
		cout << temp <<": no such directory!"<<endl;
		return -1;
	}
	//fix to allow cd'ing to dirs without '/' at the end:
	if(temp[strlen(temp)-1]!='/')
		strcat(temp, "/");
	//cout << "Process::cd setting pwd to '"<<temp<<"'"<<endl;
	strcpy(cwd, temp);
	return 0;
}


//moves the file pointer to the given offset from start of file.
//If the pointer is set to an offset after the end of the file, this is o.k.
//If data is later written at this point, subsequent reads of the data in the gap
//return bytes of zeros (until data is actually written into the gap).
//The size of the file should not be updated until data is written to that point.
int Process::lseek(int pfdi, int offset) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::lseek:  bad pfdi "<<pfdi<<endl;
		return -1;
	}
	return sys->Seek(fdTable[pfdi], offset);
}



// locks file, so that only read operations are allowed.
//There is no limit on the number of processes permitted to read the file.
//No processes are allowed to write to the file while a process holds a read
//lock to the file. It is not obligatory to lock the file to read it.
//You should not block if it is locked, rather return an error.
int Process::lck_rd(int pfdi) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::lck_rd:  bad pfdi "<<pfdi<<endl;
		return -1;
	}
	return sys->lockRead(fdTable[pfdi]);
}


//locks file, so that only one process may hold the lock.
//This process may write to the file, and no other processes may write to it
//or read from it. A child does not inherit this lock.
//It is not obligatory to lock the file to write to it.
//You should not block if it is locked, rather return an error.
int Process::lck_wr(int pfdi) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::lck_wr:  bad pfdi "<<pfdi<<endl;
		return -1;
	}
	return sys->lockWrite(fdTable[pfdi]);
}


//releases the reading lock held by this process.
//No other process may release this lock.
int Process::rlslck_rd(int pfdi) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::rlslck_rd:  bad pfdi "<<pfdi<<endl;
		return -1;
	}
	return sys->releaseLockRead(fdTable[pfdi]);
}


//releases the writing lock held by this process.
//Only the process which locked the file may release the lock.
int Process::rlslck_wr(int pfdi) {
	if(pfdi<0 || pfdi>=12 || fdTable[pfdi]==-1) {
		cout << "Process::rlslck_wr:  bad pfdi "<<pfdi<<endl;
		return -1;
	}
	return sys->releaseLockWrite(fdTable[pfdi]);
}








